package lab.craft;

/**
 * 实现一个计算器，但计算的结合性有问题。递归项在右边，会自然的对应右结合，but 我们需要的是左结合。
 * additive -> multiplicative | multiplicative + additive
 * multiplicative -> primary | primary * multiplicative
 */
public class SimpleCalculator {

    public static void main(String[] args) {
        SimpleCalculator calculator = new SimpleCalculator();
        String script;
        script = "int a = b + 3;";
        System.out.println("parsing: " + script);
        SimpleLexer lexer = new SimpleLexer();
        TokenReader tokenReader = lexer.tokenize(script);
        try {
            SimpleASTNode node = calculator.intDeclare(tokenReader);
            calculator.dumpAST(node, "");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // test 2+3*5
        script = "2+3*5;";
        System.out.println(String.format("\ncalculating %s ", script));
        calculator.evaluate(script);

        // test invalid statement
        script = "2+;";
        System.out.println(String.format("\n %s, 语法错误", script));
        calculator.evaluate(script);

        // 结合性错误
        script = "2+3+4;";
        System.out.println(String.format("\n %s 结合性错误", script));
        calculator.evaluate(script);
    }

    /**
     * 输出 AST 和求值过程
     * @param script 脚本
     */
    public void evaluate(String script) {
        try {
            ASTNode tree = parse(script);
            dumpAST(tree, "");
            evaluate(tree, "");;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析脚本
     * @param code 需要解析的脚本内容
     * @return 返回根节点
     * @throws Exception
     */
    public ASTNode parse(String code) throws Exception {
        SimpleLexer lexer = new SimpleLexer();
        TokenReader tokenReader = lexer.tokenize(code);
        return prog(tokenReader);
    }

    /**
     * 打印 AST 的树状结构
     * @param node 节点
     * @param indent 缩进
     */
    private void dumpAST(ASTNode node, String indent) {
        System.out.println(indent + node.getType() + " " + node.getText());
        for (ASTNode child : node.getChildren()) {
            dumpAST(child, indent + "\t");
        }
    }

    // 计算结果
    private int evaluate(ASTNode node, String indent) throws Exception {
        int result = 0;
        String nextIndent = indent + "\t";
        System.out.println(indent + "Calculating: " + node.getType());
        ASTNode child1 = null;
        ASTNode child2 = null;
        int value1 = 0;
        int value2 = 0;
        switch (node.getType()) {
            case Program:
                for (ASTNode child : node.getChildren()) {
                    result = evaluate(child, nextIndent);
                }
                break;
            case Additive:
                // 加、减法
                child1 = node.getChildren().get(0);
                value1 = evaluate(child1, nextIndent);
                child2 = node.getChildren().get(1);
                value2 = evaluate(child2, nextIndent);
                if (node.getText().equals("+")) {
                    result = value1 + value2;
                } else {
                    result = value1 - value2;
                }
                break;
            case Multiplicative:
                child1 = node.getChildren().get(0);
                value1 = evaluate(child1, nextIndent);
                child2 = node.getChildren().get(1);
                value2 = evaluate(child2, nextIndent);
                if (node.getText().equals("*")) {
                    result = value1 * value2;
                } else {
                    if (value2 == 0) {
                        throw new Exception("divided by zero");
                    }
                    result = value1 / value2;
                }
                break;
            case IntLiteral:
                result = Integer.parseInt(node.getText());
                break;
            default:

        }
        System.out.println(indent + "Result: " + result);
        return result;
    }

    // 解析

    private SimpleASTNode prog(TokenReader tokenReader) throws Exception {
        SimpleASTNode node = new SimpleASTNode(ASTNodeType.Program, "Calculator");
        SimpleASTNode child = additive(tokenReader);
        if (child != null) {
            node.addChild(child);
        }
        return node;
    }

    /**
     * 加法表达式
     * @param tokenReader 已经读取的token
     * @return 节点
     * @throws Exception 异常
     */
    private SimpleASTNode additive(TokenReader tokenReader) throws Exception {
        SimpleASTNode child1 = multiplicative(tokenReader);
        SimpleASTNode node = child1;
        Token token = tokenReader.peek();
        if (child1 != null && token != null) {
            if (token.getType() == TokenType.Plus || token.getType() == TokenType.Minus) {
                token = tokenReader.read();
                SimpleASTNode child2 = additive(tokenReader);
                if (child2 != null) {
                    node = new SimpleASTNode(ASTNodeType.Additive, token.getText());
                    node.addChild(child1);
                    node.addChild(child2);
                } else {
                    throw new Exception("invalid additive expression, expecting the right part");
                }
            }
        }
        return node;
    }

    /**
     * 乘法表达式
     * @param tokenReader token
     * @return 乘、除法节点
     * @throws Exception 异常
     */
    private SimpleASTNode multiplicative(TokenReader tokenReader) throws Exception {
        SimpleASTNode child1 = primary(tokenReader);
        SimpleASTNode node = child1;
        Token token = tokenReader.peek();
        if (child1 != null && token != null) {
            if (token.getType() == TokenType.Star || token.getType() == TokenType.Slash) {
                token = tokenReader.read();
                SimpleASTNode child2 = primary(tokenReader);
                if (child2 != null) {
                    node = new SimpleASTNode(ASTNodeType.Multiplicative, token.getText());
                    node.addChild(child1);
                    node.addChild(child2);
                } else {
                    // 只找到了一个乘数
                    throw new Exception("invalid multiplicative expression, expecting the right part");
                }
            }
        }
        return node;
    }

    private SimpleASTNode primary(TokenReader tokenReader) throws Exception {
        SimpleASTNode node = null;
        Token token = tokenReader.peek();
        if (token != null) {
            if (token.getType() == TokenType.IntLiteral) {
                // 整型
                token = tokenReader.read();
                node = new SimpleASTNode(ASTNodeType.IntLiteral, token.getText());
            } else if (token.getType() == TokenType.Identifier) {
                // 标识符
                token = tokenReader.read();
                node = new SimpleASTNode(ASTNodeType.Identifier, token.getText());
            } else if (token.getType() == TokenType.LeftParen) {
                // 读出左括号
                tokenReader.read();
                node = additive(tokenReader);
                if (node != null) {
                    token = tokenReader.peek();
                    if (token != null && token.getType() == TokenType.RightParen) {
                        tokenReader.read();
                    } else {
                        // 缺失右括号
                        throw new Exception("expecting right parenthesis");
                    }
                } else {
                    throw new Exception("expecting an additive express inside parenthesis");
                }
            }
        }
        // AST primary 节点，AST的简化，直接返回子节点。因为它只有一个子节点
        return node;
    }

    /**
     * 声明 int，如 int a
     * @param tokenReader 取词器
     * @return
     * @throws Exception
     */
    private SimpleASTNode intDeclare(TokenReader tokenReader) throws Exception {
        SimpleASTNode node = null;
        Token token = tokenReader.peek();
        if (token != null && token.getType() == TokenType.Int) {
            // 吃掉int
            tokenReader.read();
            if (tokenReader.peek().getType() == TokenType.Identifier) {
                token = tokenReader.read();
                //创建当前节点，并把变量名记到AST节点的文本值中，这里新建一个变量子节点也是可以的
                node = new SimpleASTNode(ASTNodeType.IntDeclaration, token.getText());
                token = tokenReader.peek();
                if (token != null && token.getType() == TokenType.Assignment) {
                    // 吃掉等号
                    tokenReader.read();
                    SimpleASTNode child = additive(tokenReader);
                    if (child == null) {
                        throw new Exception("invalid variable initialization");
                    } else {
                        node.addChild(child);
                    }
                }
            } else {
                throw new Exception("variable name expected");
            }
            token = tokenReader.peek();
            if (token != null && token.getType() == TokenType.SemiColon) {
                // 吃掉;
                tokenReader.read();
            } else {
                throw new Exception("expecting semicolon");
            }
        }
        return node;
    }


}
